﻿using System.Collections;
using System.Text;

namespace Foundation.Core
{
    public class SelectItemLoadMethodConverter : ISelectItemLoadMethodConverter
    {
        private readonly SelectItemLoadMethodOptions _methodOptions;

        ///<summary> 缓存key最大长度</summary>
        private static int _cecheKeyMaxSize = 2 * 1024;

        public SelectItemLoadMethodConverter(SelectItemLoadMethodOptions methodOptions)
        {
            this._methodOptions = methodOptions;
        }

        public virtual string BuildCacheKey(IDictionary<string, object> argumets)
        {
            var isEmpty = argumets.IsNullOrEmpty();
            if (isEmpty && (_methodOptions.IsNoReferenceMethod || _methodOptions.ParametersAllOptional))
            {
                return string.Empty;
            }

            var sb = new StringBuilder();
            foreach (var p in _methodOptions.Parameters)
            {
                if (p.IsCancellationToken) continue;
                var valueStr = string.Empty;

                if (argumets.TryGetValue($"$.parameterValue.{p.PrameterName}", out object pvObj) && pvObj is ISelectParameterValue pv && pv.Success)
                {
                    valueStr = pv.FormattedValue;
                }
                else if (!isEmpty && argumets.TryGetValue(p.PrameterName, out var value))
                {
                    valueStr = value?.ToString();
                    if (p.IsSimpleListType)
                    {
                        IList<string> arr = null;

                        if (p.ItemUnderlyingType.IsEnum)
                        {
                            var vResult = ConvertValue(argumets, p);

                            if (vResult.IsSuccss)
                            {
                                var list = vResult.Value as IList;
                                if (list.Count > 0)
                                {
                                    arr = new List<string>(list.Count);

                                    foreach (var v in list)
                                    {
                                        if (v == null) continue;

                                        var s = (v as Enum).ToString("d");
                                        if (!string.IsNullOrEmpty(s))
                                        {
                                            arr.Add(s);
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            arr = SplitString(value?.ToString(), false);
                        }

                        if (arr != null && arr.Count > 0)
                        {
                            valueStr = string.Join(",", arr.OrderBy(a => a?.ToLower()));
                        }
                    }
                }
                else if (!p.IsOptional)
                {
                    var message = $"未找到参数:{p.PrameterName}";
                    throw new BusinessException("select_method_converter_Parameter_Errror", message);
                }

                sb.Append(string.Format("{0}={1}&", p.PrameterName.ToLower(), valueStr));
            }

            var result = sb.ToString();

            if (result.Length > _cecheKeyMaxSize) throw new ArgumentException($"请求参数值过多");

            return result;
        }

        public virtual SelectItemLoadMethodParameterConvertResult ConvertValue(IDictionary<string, object> argumets, SelectItemLoadMethodParameter parameter)
        {
            if (argumets.IsNullOrEmpty()) return default;

            if (argumets.TryGetValue($"$.parameterValue.{parameter.PrameterName}", out object pvObj) && pvObj is ISelectParameterValue pv)
            {
                return new SelectItemLoadMethodParameterConvertResult
                {
                    IsSuccss = pv.Success,
                    Value = pv.Value,
                    IsFound = true,
                    IsSupport = true
                };
            }

            if (!argumets.TryGetValue(parameter.PrameterName, out var obj)) return default;

            var valueFactory = GetOrCreateFactory(parameter);
            var isSupport = valueFactory != null;

            if (isSupport)
            {
                try
                {
                    var str = obj?.ToString();
                    var isEmpty = string.IsNullOrEmpty(str);
                    var isSuccss = false;
                    object value = parameter.ParameterInfo.ParameterType.DefaultValue();

                    if (isEmpty)
                    {
                        if (parameter.IsOptional || parameter.IsNullable || parameter.IsListType)
                        {
                            value = parameter.HasDefaultValue ? parameter.DefaultValue :
                                (parameter.IsListType
                                    ? parameter.ParameterType.IsArray
                                        ? Array.CreateInstance(parameter.ItemUnderlyingType, 0)
                                        : Activator.CreateInstance(typeof(List<>).MakeGenericType(parameter.ItemType))
                                    : value
                                );
                            isSuccss = true;
                        }
                    }
                    else
                    {
                        if (parameter.IsListType)
                        {
                            var arr = SplitString(str, false, parameter.ItemUnderlyingType == typeof(string));
                            var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(parameter.ItemType), new object[] { arr.Count }) as IList;

                            foreach (var item in arr)
                            {
                                var itemValue = valueFactory(item);

                                list.Add(itemValue);
                            }

                            if (parameter.ParameterType.IsArray)
                            {
                                var array = Array.CreateInstance(parameter.ItemType, list.Count);
                                list.CopyTo(array, 0);
                                list = array;
                            }
                            value = list;
                        }
                        else if (parameter.IsSimpleType)
                        {
                            //value = Convert.ChangeType(valueFactory(str), parameter.ParameterType);
                            value = valueFactory(str);
                        }

                        isSuccss = true;
                    }

                    return new SelectItemLoadMethodParameterConvertResult { IsSuccss = isSuccss, IsFound = true, IsSupport = isSupport, Value = value };
                }
                catch (Exception _)
                {
                }
            }

            return new SelectItemLoadMethodParameterConvertResult { IsFound = true, IsSupport = isSupport };
        }

        protected IList<string> SplitString(string str, bool includeEmpty, bool isStringType = false)
        {
            var arr = str?.Trim().TrimStart('[').TrimEnd(']').Split(new[] { ',' }, includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);

            var result = new List<string>((arr?.Length).GetValueOrDefault());

            foreach (var item in arr)
            {
                var itemStr = item;

                if (itemStr.StartsWith("\"") && itemStr.EndsWith("\""))
                {
                    itemStr = item.Trim('"');
                }
                else if (itemStr.StartsWith("'") && itemStr.EndsWith("'"))
                {
                    itemStr = item.Trim('\'');
                }

                if (includeEmpty || !string.IsNullOrEmpty(itemStr))
                {
                    result.Add(isStringType ? itemStr : itemStr.Trim());
                }
            }

            return result;
        }

        protected virtual Func<string, object> GetOrCreateFactory(SelectItemLoadMethodParameter parameter)
        {
            var factory = parameter.ValueFactory;

            if (!parameter.HasSetValueFactory)
            {
                if (parameter.IsSimpleType)
                {
                    factory = CreateFactory(parameter.ParameterUnderlyingType);
                }
                else if (parameter.IsSimpleListType)
                {
                    factory = CreateFactory(parameter.ItemUnderlyingType);
                }

                parameter.ValueFactory = factory;
                parameter.HasSetValueFactory = true;
            }

            return factory;
        }

        protected virtual Func<string, object> CreateFactory(Type type)
        {
            Func<string, object> factory = null;

            //bool、short、ushort、int、uint、long、ulong、IntPtr、UIntPtr、char、double、enum、datetime

            if (type == typeof(string))
            {
                factory = str => str;
            }
            else if (type == typeof(bool))
            {
                factory = (str) => Convert.ToBoolean(str);
            }
            else if (type == typeof(short))
            {
                factory = (str) => Convert.ToInt16(str);
            }
            else if (type == typeof(ushort))
            {
                factory = (str) => Convert.ToUInt16(str);
            }
            else if (type == typeof(int))
            {
                factory = (str) => Convert.ToInt32(str);
            }
            else if (type == typeof(uint))
            {
                factory = (str) => Convert.ToUInt32(str);
            }
            else if (type == typeof(long))
            {
                factory = (str) => Convert.ToInt64(str);
            }
            else if (type == typeof(ulong))
            {
                factory = (str) => Convert.ToUInt64(str);
            }
            else if (type == typeof(char))
            {
                factory = (str) => Convert.ToChar(str);
            }
            else if (type == typeof(DateTime))
            {
                factory = (str) => Convert.ToDateTime(str);
            }
            else if (type == typeof(decimal))
            {
                factory = (str) => decimal.Parse(str);
            }
            else if (type.IsEnum)
            {
                factory = (str) => Enum.Parse(type, str);
            }
            else if (type.IsPrimitive)
            {
                factory = (str) => Convert.ChangeType(str, type);
            }

            return factory;
        }
    }
}
